<template>
  <div>
    <note-templates :data="stringData" :list="directory">
      <template #note>
        <div id="note-item1">
          <title-card title="封装" />
          <p><b>websocket.js</b></p>
          <pre-code :code="code1" language="JavaScript" />
        </div>
        <div id="note-item2">
          <title-card title="创建链接" />
          <pre-code :code="createSleepWs" language="JavaScript" />
        </div>
        <div id="note-item3">
          <title-card title="注销" />
          <pre-code :code="destroyed" language="JavaScript" />
        </div>
      </template>
    </note-templates>
  </div>
</template>

<script setup name="PcWebSocket">
import NoteTemplates from '@/components/NoteTemplates/index.vue';
import TitleCard from '@/components/TitleCard/index.vue';
import { postArticleDetail } from '@/api/home.js';
import { onMounted, reactive, ref, toRefs } from 'vue';

const code1 = ref(`/**
 * 发起websocket请求函数
 * @param {object} wsObj  - ws对象
 * @param {string} type  - 操作websocket：销毁close、创建create
 * @param {number} timeout  - 心跳间隔时长，默认5000ms
 * @param sendHeartBeat  - 以心跳，内容体区分string、object
 * @param {function} msgCallback  - 接收到ws数据，对数据进行处理的回调函数
 * @param {function} reCallback  - ws每次重连时，暴露对外的回调函数
 */
export function websocketCommand(wsObj,type,timeout=5000, sendHeartBeat, msgCallback, reCallback) {
    let wsDestroy = type ==='close';  //  销毁标志
    let lockReconnect = false;  // 是否真正建立连接
    let timeoutObj = null;  // 心跳倒计时
    let serverTimeoutObj = null;  // 服务器心跳倒计时
    let timeoutnum = null;  // 断开 重连倒计时

    // 若type传入close，则意图销毁websocket
    if(type==='close'){
        clearTimeout(timeoutObj);
        clearTimeout(serverTimeoutObj);
        onClose();
    }
    // 若type传入create，则意图新建websocket，需初始化ws并发送心跳
    if(type==='create'){
        initWebsocket();
        websocketSend();
    }

    function initWebsocket(){
        if (typeof (WebSocket) === 'undefined') {
            console.log();('您的浏览器不支持WebSocket，无法获取数据');
            return false;
        }
        wsObj.onmessage = function (e) { onMessage(e) };
        wsObj.onopen = function () { onOpen() };
        wsObj.onerror = function () { onError() };
        wsObj.onclose = function () { onClose() } ;
    }

    function websocketSend () {
        // 加延迟是为了尽量让ws连接状态变为OPEN
        setTimeout(() => {
            // 添加状态判断，当为OPEN时，发送消息
            if (wsObj.readyState === wsObj.OPEN) { // wsObj.OPEN = 1
                // console.log('类型',typeof sendHeartBeat);
                if( typeof sendHeartBeat == 'string'){
                    // 若发送基本类型数据作为心跳，如字符串，直接将参数发送给服务端
                    wsObj.send(sendHeartBeat)
                }else{
                    // 若发送复杂类型数据作为心跳，如对象，则以回调方式暴露出去（得以支持动态数据）
                    sendHeartBeat();
                }
            }
            // if (wsObj.readyState === wsObj.CLOSED) { // wsObj.CLOSED = 3
            //   console.log('readyState=3')
            // }
        }, 500)
    }

    function onMessage(evt) {
        var received_msg = evt && JSON.parse(evt.data);
        msgCallback(received_msg);

        // 收到服务器信息, 重置服务器心跳
        start();
    }

    function onError() {
        console.log('ws_error');
        // 断网重连机制
        if(!wsDestroy){
            reconnect();
        }
    }

    function onOpen() {
        console.log("ws_open");
        // 连接成功向服务器发送信息，并开启心跳
        websocketSend();
        start();
    }

    function reconnect() {
        // 避免重复建立连接
        if (lockReconnect) {
            return;
        }
        lockReconnect = true;
        // 没连接上会一直重连，设置延迟避免请求过多
        timeoutnum && clearTimeout(timeoutnum);
        timeoutnum = setTimeout(function() {
            // 重连
            initWebsocket();
            // 若重连后有需额外处理的逻辑，通过reCallback()回调暴露出去
            // reCallback?.();
            lockReconnect = false;
        }, timeout);
    }

    function start() {
        // 清计时器
        timeoutObj && clearTimeout(timeoutObj);
        serverTimeoutObj && clearTimeout(serverTimeoutObj);
        // 开启心跳
        timeoutObj = setTimeout(function() {
            if (wsObj.readyState == 1) {
                // 如果连接正常，发送心跳（服务端收到后会返回一条消息）
                websocketSend();
            } else {
                // 否则重连
                reconnect();
            }
            // 超时关闭
            serverTimeoutObj = setTimeout(function() {
                wsObj.close();
            }, timeout);
        }, timeout);
    }

    function onClose() {
        if(!wsDestroy ){
            // 重连机制
            reconnect();
        }else if (wsObj.readyState == 1) {
            console.log('ws_close',wsObj);
            // 如果ws连接正常，则清计时器、断开连接
            clearTimeout(timeoutObj);
            clearTimeout(serverTimeoutObj);
            wsObj.close();
        }
    }

}`);
const createSleepWs = ref(`import { websocketCommand } from "@utils/websocket";

// 建立连接、发起ws请求，以心跳方式，向服务端发送数据
createSleepWs() {
  let webscoketSleepUrl = "http:xxx.com"; //请求地址
  this.wsSleepObj = new WebSocket(webscoketSleepUrl);
  // 若为字符串类型，直接传参
  // websocketCommand(this.wsSleepObj, 'create', 1000, 'heartbeat');
  // 若为对象类型，以回调方式发送
  websocketCommand(
    this.wsSleepObj, "create", 4000, this.sendHeartSleepBeat, this.receiveSleepMsg, this.reconnectSleepWs
  );
},

//  ws连接成功，后台返回的ws数据
receiveSleepMsg(data) {
  // 收到服务端发送的数据后，需要处理的逻辑
  // 如：this.$store.dispatch();
  console.log(data);
},

// 断网重连，需要处理的逻辑
reconnectSleepWs() {
  if (this.wsSleepObj) {
    this.createSleepWs();
    // 如：this.$store.dispatch("app/changeAppMsg",['cloudWebsocketReconnect',true]);
  }
},

// 以回调方式向服务端发送(对象类型的)心跳
sendHeartSleepBeat(arr) {
  let sendObj = {}; // 请求传的对象
  this.wsSleepObj.send(JSON.stringify(sendObj));
},
`);
const destroyed = ref(`// 页面跳转（生命周期）
beforeRouteLeave(to, from, next) {
  if (this.wsSleepObj) {
    websocketCommand(this.wsSleepObj, "close"); // 清除websocket
    this.wsSleepObj = null;
  }
  next();
},
// 页面注销（生命周期）
destroyed() {
  if (this.wsSleepObj) {
    websocketCommand(this.wsSleepObj, "close");
    this.wsSleepObj = null;
  }
},`);

const directory = ref([
  { 
    name:'封装',
    id: 'note-item1'
  },
  { 
    name:'创建链接',
    id: 'note-item2'
  },
  { 
    name:'注销',
    id: 'note-item3'
  },
]);

const data = reactive({
  stringData: {},
});
const { stringData } = toRefs(data);
const getTitle = () => {
  postArticleDetail({
    route: 'PcWebSocket',
  }).then((res) => {
    stringData.value = res.data;
  });
};
onMounted(() => {
  getTitle();
});
</script>
